package com.waijiaojun.tpo;

import com.waijiaojun.tpo.service.account.ShiroDbRealm;
import com.waijiaojun.tpo.service.account.WeixinOAuthAuthenticationFilter;
import com.waijiaojun.tpo.utils.Constants;
import com.waijiaojun.tpo.web.admin.AdminCategoryController;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
@SpringBootApplication
@ServletComponentScan
public class TpoApplication extends SpringBootServletInitializer {
    private static Logger logger = LoggerFactory.getLogger(TpoApplication.class);
    @Value("${weixin.oauth.test.model}")
    private boolean testUserModel =false ;//Constants.testUserModel;

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(TpoApplication.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(TpoApplication.class, args);
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter() {
//        http://192.168.15.42:8821/tpo/login/webLogin/123
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        Map<String, String> filterChainDefinitionMapping = new LinkedHashMap<>();//必须使用可排序的Map
        logger.info(":::: :::: testUserModel："+testUserModel);
        if(testUserModel) {
            //为了方便开发和测试业务贡呢：开发模式下可以匿名访问任何资源，并且通过UserUtil.getCurrentUserOpenId()获取一个固定配置的用户信息
            filterChainDefinitionMapping.put("/**", "anon");// 去掉权限
            logger.info(":::: :::: *** anon");
        }else{
            filterChainDefinitionMapping.put("/static/**", "anon");
            filterChainDefinitionMapping.put("/admin/**", "anon");
            filterChainDefinitionMapping.put("/aaa.html", "anon");
            filterChainDefinitionMapping.put("/favicon.ico", "anon");
            filterChainDefinitionMapping.put("/login/webLogin/**", "anon");
            filterChainDefinitionMapping.put("/login/oauthFailure", "anon");
            filterChainDefinitionMapping.put("/weixinOAuthLogin", "weixinOAuthAuthentication");
            filterChainDefinitionMapping.put("/weixin-pay-callback/**", "anon");
            filterChainDefinitionMapping.put("/logout", "logout");
            filterChainDefinitionMapping.put("/wxserver/**", "anon");
            filterChainDefinitionMapping.put("/img/**", "anon");
            filterChainDefinitionMapping.put("/css/**", "anon");
            filterChainDefinitionMapping.put("/js/**", "anon");
            filterChainDefinitionMapping.put("/oauthLogin.html", "anon");
            filterChainDefinitionMapping.put("/MP_verify_F8gzVCOzo7Ej2z4Y.txt", "anon");
            filterChainDefinitionMapping.put("/MP_verify_t875LPXuURN8Wvw8.txt", "anon");
            filterChainDefinitionMapping.put("/", "user");
            filterChainDefinitionMapping.put("/**", "user");

            Map<String, Filter> filters = new HashMap<String, Filter>();
            WeixinOAuthAuthenticationFilter oauthFilter = new WeixinOAuthAuthenticationFilter();
            oauthFilter.setAuthcCodeParam("code");
            oauthFilter.setFailureUrl("/login/oauthFailure");
            oauthFilter.setResponseType("code");
            filters.put("weixinOAuthAuthentication",oauthFilter );
            shiroFilter.setFilters(filters);

            shiroFilter.setLoginUrl("/login");
            shiroFilter.setSuccessUrl("/index");
            shiroFilter.setUnauthorizedUrl("/forbidden");
            shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMapping);
        }
        shiroFilter.setSecurityManager(securityManager());
        return shiroFilter;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());
        return securityManager;
    }

    @Bean(name = "realm")
    @DependsOn("lifecycleBeanPostProcessor")
    public Realm realm() {
        return new ShiroDbRealm();
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 1. springboot集成shiro，配置@Transaction报错
     起因是，使用springboot集成mybatis，在进行事务控制的时候，我给@Transaction加在@Service和@Controller上面，都会报错，加在@Controller上面报404错误，加在@Service上面，直接启动不了，在启动的时候

     The bean ‘goodsOrderService’ could not be injected as a ‘com.waijiaojun.tpo.service.GoodsOrderService’ because it is a JDK dynamic proxy that implements:

     加了@Transaction的类会自动开启动态代理，java的代理机制主要有JDK动态代理和CGLIB，报上面的错误是因为使用了JDK动态代理机制，因为我这个@Service不是接口实现类，所以就报错了。所以，我目前的解决方案是将@Service类变为接口的实现，运行正常(但是@Transaction还是无法标记在@Controller上(虽然不推荐标记在@Controller))。我尝试在开启@Transaction设置@EnableTransactionManagement(proxyTargetClass = true)，尝试使用CGLIB代理，但是依然不起作用。
     早晨我重新创建了一个项目，只集成了mybatis，配置@Transaction无论是加在@Controller或者@Service，缺都没有报错。所以，我觉得问题应该出在，我使用SpringBoot集成其他功能时导致了问题的出现，最后排查发现，在集成shiro过程中，有如下代码（用来开启shiro注解）

     @Bean
     @DependsOn("lifecycleBeanPostProcessor")
     public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
     DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
     //creator.setProxyTargetClass(true);
     return creator;
     }
     其中，setProxyTargetClass()方法注释如下，默认只代理接口。设置为true时，将根据目标类类型来代理，如果是接口，使用JDK动态代理，如果是类则使用CGLIB。

     Set whether to proxy the target class directly, instead of just proxying specific interfaces. Default is “false”.
     Set this to “true” to force proxying for the TargetSource’s exposed target class. If that target class is an interface, a JDK proxy will be created for the given interface. If that target class is any other class, a CGLIB proxy will be created for the given class.

     所以，最终的解决方案是设置DefaultAdvisorAutoProxyCreator的proxyTargetClass为true
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }
}
